/*  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "luge-util.hh"
#include "luge-color.hh"
#include "luge-pad.hh"
#include <iostream>
#include <sstream>

/**
 * convert a gvalue to a string.
 */
std::string luge::gvalue_to_string (const GValue &value)
{
  std::ostringstream oss;
  if (G_VALUE_HOLDS_STRING (&value)) {
    oss << '"' << g_value_get_string (&value) << '"';
  } else if (G_VALUE_HOLDS_INT (&value)) {
    oss << g_value_get_int (&value);
  } else if (G_VALUE_HOLDS_DOUBLE (&value)) {
    oss << g_value_get_double (&value);
  } else if (G_VALUE_HOLDS_BOOLEAN (&value)) {
    if (g_value_get_boolean (&value)) {
      oss << "true";
    } else {
      oss << "false";
    }
  } else if (G_VALUE_HOLDS (&value, GEGL_TYPE_COLOR)) {
    GeglColor *color_value
      = static_cast <GeglColor *> (g_value_get_object (&value));
    oss << "<" << luge::color_to_string (color_value) << ">";
  } else {
    oss << "<type:" << G_VALUE_TYPE (&value) << ">";
  }
  return oss.str ();
}

int luge::gvalue_push (const GValue &value, lua_State *ls)
{
  if (G_VALUE_HOLDS_STRING (&value)) {
    lua_pushstring (ls, g_value_get_string (&value));
    return 1;
  } else if (G_VALUE_HOLDS_INT (&value)) {
    lua_pushnumber (ls, g_value_get_int (&value));
    return 1;
  } else if (G_VALUE_HOLDS_DOUBLE (&value)) {
    lua_pushnumber (ls, g_value_get_double (&value));
    return 1;
  } else if (G_VALUE_HOLDS_BOOLEAN (&value)) {
    if (g_value_get_boolean (&value)) {
      lua_pushnumber (ls, 1);
    } else {
      lua_pushnil (ls);
    }
    return 1;
  } else if (G_VALUE_HOLDS (&value, GEGL_TYPE_COLOR)) {
    GeglColor *color_value
      = static_cast <GeglColor *> (g_value_get_object (&value));
    luge::push_color (ls, color_value);
    return 1;
  } else {
    return 0;
  }
}

GeglNode *luge::clone_node (GeglNode *node)
{
  char *opname = 0;
  gegl_node_get (node, "operation", &opname, (char *) 0);
  /*
   * create an empty node with the same operation.
   */
  GeglNode *clone = gegl_node_new ();
  gegl_node_set (clone, "operation", opname, (char *) 0);
  /*
   * copy all parameters to the clone.
   */
  guint nparam = 0;
  GParamSpec **params = gegl_operation_list_properties (opname, &nparam);
  unsigned p_idx = 0;
  while (p_idx != nparam) {
    GParamSpec *param = params[p_idx];
    if (luge::is_io_prop (node, param->name)) {
      if (luge::is_input_prop (node, param->name)) {
        /*
         * for input properties: connect them to the same source as for the
         * node to be cloned.
         */
        gchar *source_pad = 0;
        GeglNode *source_node
          = gegl_node_get_producer (node, (gchar *) param->name, &source_pad);
        if (source_node != 0) {
          gegl_node_connect_from (clone, param->name, source_node, source_pad);
          g_free (source_pad);
        }
      }
    } else if (param->flags & G_PARAM_WRITABLE) {
      /*
       * not a pad; try to copy the value using generic values.
       */
      GValue value = { 0 };
      g_value_init (&value, param->value_type);
      gegl_node_get_property (node, param->name, &value);
      gegl_node_set_property (clone, param->name, &value);
      g_value_unset (&value);
    }
    ++ p_idx;
  }
  return clone;
}

/**
 * convert a node to a short string.
 */
std::string luge::node_toshort (GeglNode *node)
{
  char *opname = 0;
  gegl_node_get (node, "operation", &opname, (char *) 0);
  std::ostringstream oss;
  oss << "<" << opname << node << ">";
  return oss.str ();
}

/**
 * convert a node to a string.
 * mainly used for output debug information of nodes.
 * the general format returned is:
 *   <operation> "(" <attribute> "=" <value> ","...")".
 */
std::string luge::node_tostring (GeglNode *node)
{
  char *opname = 0;
  gegl_node_get (node, "operation", &opname, (char *) 0);
  std::ostringstream oss;
  oss << opname << "(";
  guint param_len = 0;
  GParamSpec **params = gegl_operation_list_properties (opname, &param_len);
  unsigned param_idx = 0;
  while (param_idx != param_len) {
    if (param_idx != 0) {
        oss << ", ";
    }
    GParamSpec *param = params[param_idx];
    if (luge::is_io_prop (node, param->name)) {
      luge::pad output_pad (luge::get_output_pad (node, param->name));
      oss << param->name << "=" << output_pad.to_string ();
    } else {
      GValue value = G_VALUE_INIT;
      g_value_init (&value, param->value_type);
      gegl_node_get_property (node, param->name, &value);
      oss << param->name << "=" << gvalue_to_string (value);
      g_value_reset (&value);
    }
    ++ param_idx;
  }
  oss << ")";
  return oss.str ();
}
